// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:mockito/mockito.dart';

class TestHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return createMockImageHttpClient(context);
  }
}

MockHttpClient createMockImageHttpClient(SecurityContext? _) {
  final MockHttpClient client = MockHttpClient();
  final MockHttpClientRequest request = MockHttpClientRequest();
  final MockHttpClientResponse response = MockHttpClientResponse();
  final MockHttpHeaders headers = MockHttpHeaders();

  final List<int> transparentImage = getTestImageData();

  when(client.getUrl(any))
      .thenAnswer((_) => Future<MockHttpClientRequest>.value(request));

  when(request.headers).thenReturn(headers);

  when(request.close())
      .thenAnswer((_) => Future<MockHttpClientResponse>.value(response));

  when(client.autoUncompress = any).thenAnswer((_) => null);

  when(response.contentLength).thenReturn(transparentImage.length);

  when(response.statusCode).thenReturn(HttpStatus.ok);

  when(response.compressionState)
      .thenReturn(HttpClientResponseCompressionState.notCompressed);

  // Define an image stream that streams the mock test image for all
  // image tests that request an image.
  StreamSubscription<List<int>> imageStream(Invocation invocation) {
    final void Function(List<int>)? onData =
        invocation.positionalArguments[0] as Function(List<int>)?;
    final void Function()? onDone =
        invocation.namedArguments[#onDone] as Function()?;
    final void Function(Object, [StackTrace?])? onError =
        invocation.namedArguments[#onError] as Function(Object, [StackTrace?])?;
    final bool? cancelOnError =
        invocation.namedArguments[#cancelOnError] as bool?;

    return Stream<List<int>>.fromIterable(<List<int>>[transparentImage]).listen(
      onData,
      onError: onError,
      onDone: onDone,
      cancelOnError: cancelOnError,
    );
  }

  when(response.listen(any,
          onError: anyNamed('onError'),
          onDone: anyNamed('onDone'),
          cancelOnError: anyNamed('cancelOnError')))
      .thenAnswer(imageStream);

  return client;
}

// This string represents the hexidecial bytes of a transparent image. A
// string is used to make the visual representation of the data compact. A
// List<int> of the same data requires over 60 lines in a source file with
// each element in the array on a single line.
const String _imageBytesAsString = '''
  0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49,
  0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
  0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44,
  0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D,
  0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
  ''';

// Convert the string representing the hexidecimal bytes in the image into
// a list of integers that can be consumed as image data in a stream.
final List<int> _transparentImage = const LineSplitter()
    .convert(_imageBytesAsString.replaceAllMapped(
        RegExp(r' *0x([A-F0-9]{2}),? *\n? *'), (Match m) => '${m[1]}\n'))
    .map<int>((String b) => int.parse(b, radix: 16))
    .toList();

List<int> getTestImageData() {
  return _transparentImage;
}

/// Define the "fake" data types to be used in mock data type definitions. These
/// fake data types are important in the definition of the return values of the
/// properties and methods of the mock data types for null safety.
// ignore: avoid_implementing_value_types
class _FakeDuration extends Fake implements Duration {}

class _FakeHttpClientRequest extends Fake implements HttpClientRequest {}

class _FakeUri extends Fake implements Uri {}

class _FakeHttpHeaders extends Fake implements HttpHeaders {}

class _FakeHttpClientResponse extends Fake implements HttpClientResponse {}

class _FakeSocket extends Fake implements Socket {}

class _FakeStreamSubscription<T> extends Fake
    implements StreamSubscription<T> {}

/// A class which mocks [HttpClient].
///
/// See the documentation for Mockito's code generation for more information.
class MockHttpClient extends Mock implements HttpClient {
  MockHttpClient() {
    throwOnMissingStub(this);
  }

  @override
  Duration get idleTimeout =>
      super.noSuchMethod(Invocation.getter(#idleTimeout),
          returnValue: _FakeDuration()) as Duration;

  @override
  set idleTimeout(Duration? idleTimeout) =>
      super.noSuchMethod(Invocation.setter(#idleTimeout, idleTimeout));

  @override
  bool get autoUncompress =>
      super.noSuchMethod(Invocation.getter(#autoUncompress), returnValue: false)
          as bool;

  @override
  set autoUncompress(bool? autoUncompress) =>
      super.noSuchMethod(Invocation.setter(#autoUncompress, autoUncompress));

  @override
  Future<HttpClientRequest> open(
          String? method, String? host, int? port, String? path) =>
      super.noSuchMethod(
          Invocation.method(#open, <Object?>[method, host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> openUrl(String? method, Uri? url) =>
      super.noSuchMethod(Invocation.method(#openUrl, <Object?>[method, url]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> get(String? host, int? port, String? path) =>
      super.noSuchMethod(Invocation.method(#get, <Object?>[host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> getUrl(Uri? url) => super.noSuchMethod(
          Invocation.method(#getUrl, <Object?>[url]),
          returnValue:
              Future<_FakeHttpClientRequest>.value(_FakeHttpClientRequest()))
      as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> post(String? host, int? port, String? path) =>
      super.noSuchMethod(Invocation.method(#post, <Object?>[host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> postUrl(Uri? url) => super.noSuchMethod(
          Invocation.method(#postUrl, <Object?>[url]),
          returnValue:
              Future<_FakeHttpClientRequest>.value(_FakeHttpClientRequest()))
      as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> put(String? host, int? port, String? path) =>
      super.noSuchMethod(Invocation.method(#put, <Object?>[host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> putUrl(Uri? url) => super.noSuchMethod(
          Invocation.method(#putUrl, <Object?>[url]),
          returnValue:
              Future<_FakeHttpClientRequest>.value(_FakeHttpClientRequest()))
      as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> delete(String? host, int? port, String? path) =>
      super.noSuchMethod(
          Invocation.method(#delete, <Object?>[host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> deleteUrl(Uri? url) => super.noSuchMethod(
          Invocation.method(#deleteUrl, <Object?>[url]),
          returnValue:
              Future<_FakeHttpClientRequest>.value(_FakeHttpClientRequest()))
      as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> patch(String? host, int? port, String? path) =>
      super.noSuchMethod(Invocation.method(#patch, <Object?>[host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> patchUrl(Uri? url) => super.noSuchMethod(
          Invocation.method(#patchUrl, <Object?>[url]),
          returnValue:
              Future<_FakeHttpClientRequest>.value(_FakeHttpClientRequest()))
      as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> head(String? host, int? port, String? path) =>
      super.noSuchMethod(Invocation.method(#head, <Object?>[host, port, path]),
          returnValue: Future<_FakeHttpClientRequest>.value(
              _FakeHttpClientRequest())) as Future<HttpClientRequest>;

  @override
  Future<HttpClientRequest> headUrl(Uri? url) => super.noSuchMethod(
          Invocation.method(#headUrl, <Object?>[url]),
          returnValue:
              Future<_FakeHttpClientRequest>.value(_FakeHttpClientRequest()))
      as Future<HttpClientRequest>;

  @override
  void addCredentials(
          Uri? url, String? realm, HttpClientCredentials? credentials) =>
      super.noSuchMethod(Invocation.method(
          #addCredentials, <Object?>[url, realm, credentials]));

  @override
  void addProxyCredentials(String? host, int? port, String? realm,
          HttpClientCredentials? credentials) =>
      super.noSuchMethod(Invocation.method(
          #addProxyCredentials, <Object?>[host, port, realm, credentials]));

  @override
  void close({bool? force = false}) => super.noSuchMethod(
      Invocation.method(#close, <Object?>[], <Symbol, Object?>{#force: force}));
}

/// A class which mocks [HttpClientRequest].
///
/// See the documentation for Mockito's code generation for more information.
class MockHttpClientRequest extends Mock implements HttpClientRequest {
  MockHttpClientRequest() {
    throwOnMissingStub(this);
  }

  @override
  bool get persistentConnection =>
      super.noSuchMethod(Invocation.getter(#persistentConnection),
          returnValue: false) as bool;

  @override
  set persistentConnection(bool? persistentConnection) => super.noSuchMethod(
      Invocation.setter(#persistentConnection, persistentConnection));

  @override
  bool get followRedirects => super
          .noSuchMethod(Invocation.getter(#followRedirects), returnValue: false)
      as bool;

  @override
  set followRedirects(bool? followRedirects) =>
      super.noSuchMethod(Invocation.setter(#followRedirects, followRedirects));

  @override
  int get maxRedirects =>
      super.noSuchMethod(Invocation.getter(#maxRedirects), returnValue: 0)
          as int;

  @override
  set maxRedirects(int? maxRedirects) =>
      super.noSuchMethod(Invocation.setter(#maxRedirects, maxRedirects));

  @override
  int get contentLength =>
      super.noSuchMethod(Invocation.getter(#contentLength), returnValue: 0)
          as int;

  @override
  set contentLength(int? contentLength) =>
      super.noSuchMethod(Invocation.setter(#contentLength, contentLength));

  @override
  bool get bufferOutput =>
      super.noSuchMethod(Invocation.getter(#bufferOutput), returnValue: false)
          as bool;

  @override
  set bufferOutput(bool? bufferOutput) =>
      super.noSuchMethod(Invocation.setter(#bufferOutput, bufferOutput));

  @override
  String get method =>
      super.noSuchMethod(Invocation.getter(#method), returnValue: '') as String;

  @override
  Uri get uri =>
      super.noSuchMethod(Invocation.getter(#uri), returnValue: _FakeUri())
          as Uri;

  @override
  HttpHeaders get headers => super.noSuchMethod(Invocation.getter(#headers),
      returnValue: _FakeHttpHeaders()) as HttpHeaders;

  @override
  List<Cookie> get cookies =>
      super.noSuchMethod(Invocation.getter(#cookies), returnValue: <Cookie>[])
          as List<Cookie>;

  @override
  Future<HttpClientResponse> get done => super.noSuchMethod(
          Invocation.getter(#done),
          returnValue:
              Future<_FakeHttpClientResponse>.value(_FakeHttpClientResponse()))
      as Future<HttpClientResponse>;

  @override
  Future<HttpClientResponse> close() => super.noSuchMethod(
          Invocation.method(#close, <Object?>[]),
          returnValue:
              Future<_FakeHttpClientResponse>.value(_FakeHttpClientResponse()))
      as Future<HttpClientResponse>;
}

/// A class which mocks [HttpClientResponse].
///
/// See the documentation for Mockito's code generation for more information.
class MockHttpClientResponse extends Mock implements HttpClientResponse {
  MockHttpClientResponse() {
    throwOnMissingStub(this);
  }

  // Include an override method for the inherited listen method. This method
  // intercepts HttpClientResponse listen calls to return a mock image.
  @override
  StreamSubscription<List<int>> listen(void Function(List<int> event)? onData,
          {Function? onError, void Function()? onDone, bool? cancelOnError}) =>
      super.noSuchMethod(
              Invocation.method(
                #listen,
                <Object?>[onData],
                <Symbol, Object?>{
                  #onError: onError,
                  #onDone: onDone,
                  #cancelOnError: cancelOnError
                },
              ),
              returnValue: _FakeStreamSubscription<List<int>>())
          as StreamSubscription<List<int>>;

  @override
  int get statusCode =>
      super.noSuchMethod(Invocation.getter(#statusCode), returnValue: 0) as int;

  @override
  String get reasonPhrase =>
      super.noSuchMethod(Invocation.getter(#reasonPhrase), returnValue: '')
          as String;

  @override
  int get contentLength =>
      super.noSuchMethod(Invocation.getter(#contentLength), returnValue: 0)
          as int;

  @override
  HttpClientResponseCompressionState get compressionState =>
      super.noSuchMethod(Invocation.getter(#compressionState),
              returnValue: HttpClientResponseCompressionState.notCompressed)
          as HttpClientResponseCompressionState;

  @override
  bool get persistentConnection =>
      super.noSuchMethod(Invocation.getter(#persistentConnection),
          returnValue: false) as bool;

  @override
  bool get isRedirect =>
      super.noSuchMethod(Invocation.getter(#isRedirect), returnValue: false)
          as bool;

  @override
  List<RedirectInfo> get redirects =>
      super.noSuchMethod(Invocation.getter(#redirects),
          returnValue: <RedirectInfo>[]) as List<RedirectInfo>;

  @override
  HttpHeaders get headers => super.noSuchMethod(Invocation.getter(#headers),
      returnValue: _FakeHttpHeaders()) as HttpHeaders;

  @override
  List<Cookie> get cookies =>
      super.noSuchMethod(Invocation.getter(#cookies), returnValue: <Cookie>[])
          as List<Cookie>;

  @override
  Future<HttpClientResponse> redirect(
          [String? method, Uri? url, bool? followLoops]) =>
      super.noSuchMethod(
          Invocation.method(#redirect, <Object?>[method, url, followLoops]),
          returnValue: Future<_FakeHttpClientResponse>.value(
              _FakeHttpClientResponse())) as Future<HttpClientResponse>;

  @override
  Future<Socket> detachSocket() => super.noSuchMethod(
      Invocation.method(#detachSocket, <Object?>[]),
      returnValue: Future<_FakeSocket>.value(_FakeSocket())) as Future<Socket>;
}

/// A class which mocks [HttpHeaders].
///
/// See the documentation for Mockito's code generation for more information.
class MockHttpHeaders extends Mock implements HttpHeaders {
  MockHttpHeaders() {
    throwOnMissingStub(this);
  }

  @override
  int get contentLength =>
      super.noSuchMethod(Invocation.getter(#contentLength), returnValue: 0)
          as int;

  @override
  set contentLength(int? contentLength) =>
      super.noSuchMethod(Invocation.setter(#contentLength, contentLength));

  @override
  bool get persistentConnection =>
      super.noSuchMethod(Invocation.getter(#persistentConnection),
          returnValue: false) as bool;

  @override
  set persistentConnection(bool? persistentConnection) => super.noSuchMethod(
      Invocation.setter(#persistentConnection, persistentConnection));

  @override
  bool get chunkedTransferEncoding =>
      super.noSuchMethod(Invocation.getter(#chunkedTransferEncoding),
          returnValue: false) as bool;

  @override
  set chunkedTransferEncoding(bool? chunkedTransferEncoding) =>
      super.noSuchMethod(
          Invocation.setter(#chunkedTransferEncoding, chunkedTransferEncoding));

  @override
  List<String>? operator [](String? name) =>
      super.noSuchMethod(Invocation.method(#[], <Object?>[name]))
          as List<String>?;

  @override
  String? value(String? name) =>
      super.noSuchMethod(Invocation.method(#value, <Object?>[name])) as String?;

  @override
  void add(String? name, Object? value, {bool? preserveHeaderCase = false}) =>
      super.noSuchMethod(Invocation.method(#add, <Object?>[name, value],
          <Symbol, Object?>{#preserveHeaderCase: preserveHeaderCase}));

  @override
  void set(String? name, Object? value, {bool? preserveHeaderCase = false}) =>
      super.noSuchMethod(Invocation.method(#set, <Object?>[name, value],
          <Symbol, Object?>{#preserveHeaderCase: preserveHeaderCase}));

  @override
  void remove(String? name, Object? value) =>
      super.noSuchMethod(Invocation.method(#remove, <Object?>[name, value]));

  @override
  void removeAll(String? name) =>
      super.noSuchMethod(Invocation.method(#removeAll, <Object?>[name]));

  @override
  void forEach(void Function(String, List<String>)? action) =>
      super.noSuchMethod(Invocation.method(#forEach, <Object?>[action]));

  @override
  void noFolding(String? name) =>
      super.noSuchMethod(Invocation.method(#noFolding, <Object?>[name]));
}
